/*
 * Copyright (C) 2011 Nicola Bonzanni
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package nl.vu.few.ibivu.jlemon;

import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

/**
 * All <tt>Map.Entry</tt> pairs returned by methods in this class and its views
 * represent snapshots of mappings at the time they were produced.
 * 
 * @author bonzanni
 * 
 * @param <K>
 * @param <V>
 */
public abstract class AbstractVectorMap<K, V> implements Map<K, V> {

	private ArrayList<V> container;
	private Collection<V> valuesCollection;
	private Set<K> keySet;
	private Set<Map.Entry<K, V>> entrySet;

	protected abstract int getId(Object key);
	protected abstract K getItem(int id);
	
	// necessary for serialization of subclasses 
	protected AbstractVectorMap() {
		// empty
	}

	public AbstractVectorMap(Set<K> keySet, int maxSize) {
		this();
		this.keySet = keySet;
		container = new ArrayList<V>(maxSize);
		container.addAll(Collections.nCopies(maxSize, (V)null));
	}

	public AbstractVectorMap(Set<K> keySet, int maxSize, V initValue) {
		this();
		this.keySet = keySet;
		container = new ArrayList<V>(maxSize);
		container.addAll(Collections.nCopies(maxSize, initValue));
	}
	
	protected final void init(InternalState state) {
		keySet = state.keySet;
		container = state.container;
	}

	protected void adjustInternalContainer() {
		int newSize = keySet.size() - container.size();
		if (newSize > 0) {
			container.addAll(Collections.nCopies(newSize, (V) null));
		}
	}

	@Override
	public void clear() {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean containsKey(Object key) {
		return keySet.contains(key);
	}

	@Override
	public boolean containsValue(Object value) {
		return container.contains(value);
	}

	@Override
	public Set<java.util.Map.Entry<K, V>> entrySet() {
		if (entrySet == null) {
			entrySet = new AbstractSet<java.util.Map.Entry<K, V>>() {
				@Override
				public Iterator<java.util.Map.Entry<K, V>> iterator() {
					return new SnapshotEntryIterator();
				}

				@Override
				public int size() {
					return AbstractVectorMap.this.size();
				}
			};
		}
		return entrySet;
	}

	@Override
	public V get(Object key) {
		if (key == null) {
			throw new NullPointerException();
		}
		return container.get(getId(key));
	}

	@Override
	public boolean isEmpty() {
		return size() == 0;
	}

	@Override
	public V put(K key, V value) {
		if (key == null) {
			throw new NullPointerException();
		}
		if (!containsKey(key)) {
			throw new IllegalArgumentException();
		}
		return container.set(getId(key), value);
	}

	@Override
	public void putAll(Map<? extends K, ? extends V> map) {
		for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
			put(entry.getKey(), entry.getValue());
		}
	}

	public void putAll(V value) {
		Collections.fill(container, value);
	}

	@Override
	public V remove(Object key) {
		throw new UnsupportedOperationException();
	}

	@Override
	public int size() {
		return keySet.size();
	}

	@Override
	public Set<K> keySet() {
		return keySet;
	}

	@Override
	public Collection<V> values() {
		if (valuesCollection == null) {
			valuesCollection = new AbstractCollection<V>() {
				@Override
				public int size() {
					return AbstractVectorMap.this.size();
				}

				@Override
				public boolean contains(Object object) {
					return containsValue(object);
				}

				@Override
				public Iterator<V> iterator() {
					return new Iterator<V>() {
						Iterator<V> iterator = container.iterator();

						public boolean hasNext() {
							return iterator.hasNext();
						}

						public V next() {
							return iterator.next();
						}

						public void remove() {
							throw new UnsupportedOperationException();
						}
					};
				}
			};
		}
		return valuesCollection;
	}

	/**
	 * Compares the specified object to this instance, and returns {@code true}
	 * if the specified object is a map and both maps contain the same mappings.
	 * 
	 * @param object
	 *            the object to compare with this object.
	 * @return boolean {@code true} if the object is the same as this object,
	 *         and {@code false} if it is different from this object.
	 * @see #hashCode()
	 * @see #entrySet()
	 */
	@Override
	public boolean equals(Object object) {
		if (this == object) {
			return true;
		}
		if (object instanceof Map) {
			Map<?, ?> map = (Map<?, ?>) object;
			if (size() != map.size()) {
				return false;
			}

			try {
				for (Entry<K, V> entry : entrySet()) {
					K key = entry.getKey();
					V mine = entry.getValue();
					Object theirs = map.get(key);
					if (mine == null) {
						if (theirs != null || !map.containsKey(key)) {
							return false;
						}
					} else if (!mine.equals(theirs)) {
						return false;
					}
				}
			} catch (NullPointerException ignored) {
				return false;
			} catch (ClassCastException ignored) {
				return false;
			}
			return true;
		}
		return false;
	}

	/**
	 * Returns the hash code for this object. Objects which are equal must
	 * return the same value for this method.
	 * 
	 * @return the hash code of this object.
	 * @see #equals(Object)
	 */
	@Override
	public int hashCode() {
		int result = 0;
		Iterator<Map.Entry<K, V>> it = entrySet().iterator();
		while (it.hasNext()) {
			result += it.next().hashCode();
		}
		return result;
	}

	private class SnapshotEntryIterator implements Iterator<java.util.Map.Entry<K, V>>,
			java.util.Map.Entry<K, V> {
		private V value;
		private ListIterator<V> iterator = container.listIterator();

		@Override
		public boolean hasNext() {
			while (iterator.hasNext()) {
				if (getItem(iterator.nextIndex()) == null) {
					iterator.next();
					continue;
				}
				else return true;
			}
			return false;
		}

		@Override
		public Map.Entry<K, V> next() {
			value = iterator.next();
			return this;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}

		@Override
		public K getKey() {
			return getItem(iterator.previousIndex());
		}

		@Override
		public V getValue() {
			return value;
		}

		@Override
		public V setValue(V value) {
			iterator.set(value);
			return this.value;
		}

		@Override
		public int hashCode() {
			K key = getKey();
			return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			}
			K key = getKey();
			if (obj instanceof Map.Entry) {
				Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
				return (key == null ? entry.getKey() == null : key.equals(entry.getKey()))
						&& (value == null ? entry.getValue() == null : value.equals(entry
								.getValue()));
			}
			return false;
		}
	}
	
	protected final InternalState getInternalState() {
		InternalState state = new InternalState();
		state.container = this.container;
		state.keySet = this.keySet;
		return state;
	}
	
	@SuppressWarnings("serial")
	protected class InternalState implements Serializable {
		private ArrayList<V> container;
		private Set<K> keySet;
		protected InternalState() {}
	}

}
